﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;

namespace EdiNet
{
    public class EdiXmlReader : XmlReader
    {
        const int MAX_ELEMENT_COUNT = 255;
        static readonly string[] ELEMENT_NAMES;
        static readonly string[] COMPOSITE_ELEMENT_NAMES;

        static EdiXmlReader()
        {
            ELEMENT_NAMES = new string[MAX_ELEMENT_COUNT];
            COMPOSITE_ELEMENT_NAMES = new string[MAX_ELEMENT_COUNT];

            for (int i = 0; i < MAX_ELEMENT_COUNT; ++i)
            {
                ELEMENT_NAMES[i] = "E" + i.ToString();
                COMPOSITE_ELEMENT_NAMES[i] = "C" + i.ToString();
            }
        }

        enum EdiXmlReaderState
        {
            RootElement,
            SegmentElement,
            DataElement,
            ComponentElement,
            Value,
            ComponentElementEnd,
            DataElementEnd,
            SegmentEnd,
            RootElementEnd,
            EndOfFile,
        }

        NameTable nameTable;
        EdiStreamReader baseReader;
        ReadState xmlReaderState;
        int depth;
        string localName;
        XmlNodeType nodeType;
        string value;

        EdiXmlReaderState internalState;

        DataSegment currentSeg;
        DataElement currentElement;
        int currentElementIndex;
        int currentComponentIndex;
        string rootElementName;
        const string DEFAULT_ROOT_ELEMENT_NAME = "EdiXml";

        public EdiXmlReader(Stream ediStream)
            : this(ediStream, DefaultEdiEncoding.Encoding)
        {
        }
        public EdiXmlReader(Stream ediStream, Encoding enc)
            : this(new EdiStreamReader(ediStream, enc))
        {
        }
        public EdiXmlReader(EdiStreamReader ediStreamReader)
            : this(DEFAULT_ROOT_ELEMENT_NAME, ediStreamReader)
        {
        }
        public EdiXmlReader(string rootElementName, EdiStreamReader ediStreamReader)
        {
            this.baseReader = ediStreamReader;
            this.nameTable = new NameTable();
            this.xmlReaderState = ReadState.Initial;
            this.depth = 0;
            this.localName = string.Empty;
            this.nodeType = XmlNodeType.None;
            this.value = string.Empty;
            this.rootElementName = nameTable.Add(rootElementName);
        }
        public override int AttributeCount
        {
            get { return 0; }
        }

        public override string BaseURI
        {
            get { return string.Empty; }
        }

        public override void Close()
        {
            baseReader.Close();
            xmlReaderState = ReadState.Closed;
        }

        public override int Depth
        {
            get { return depth; }
        }

        public override bool EOF
        {
            get { return xmlReaderState == ReadState.EndOfFile; }
        }

        public override string GetAttribute(int i)
        {
            throw new ArgumentOutOfRangeException();
        }

        public override string GetAttribute(string name, string namespaceURI)
        {
            return null;
        }

        public override string GetAttribute(string name)
        {
            return null;
        }

        public override bool IsEmptyElement
        {
            get { return false; }
        }

        public override string LocalName
        {
            get { return localName; }
        }

        public override string LookupNamespace(string prefix)
        {
            return null;
        }

        public override bool MoveToAttribute(string name, string ns)
        {
            return false;
        }

        public override bool MoveToAttribute(string name)
        {
            return false;
        }

        public override bool MoveToElement()
        {
            return false;
        }

        public override bool MoveToFirstAttribute()
        {
            return false;
        }

        public override bool MoveToNextAttribute()
        {
            return false;
        }

        public override XmlNameTable NameTable
        {
            get { return nameTable; }
        }

        public override string NamespaceURI
        {
            get { return string.Empty; }
        }

        public override XmlNodeType NodeType
        {
            get { return this.nodeType; }
        }

        public override string Prefix
        {
            get { return string.Empty; }
        }

        public override bool Read()
        {
            switch (xmlReaderState)
            {
                case ReadState.EndOfFile:
                case ReadState.Closed:
                    return false;
                case ReadState.Initial:
                    xmlReaderState = ReadState.Interactive;
                    break;
            }

            switch (internalState)
            {
                case EdiXmlReaderState.RootElement:
                    localName = rootElementName;
                    nodeType = XmlNodeType.Element;
                    NextSegment();
                    break;
                case EdiXmlReaderState.SegmentElement:
                    localName = nameTable.Add(currentSeg.SegId);
                    depth = 1;
                    nodeType = XmlNodeType.Element;
                    NextElement();
                    break;
                case EdiXmlReaderState.DataElement:
                    bool isSimple = currentElement.IsSimpleDataElement();
                    localName = nameTable.Add(ELEMENT_NAMES[isSimple ? currentElementIndex : currentComponentIndex + 1]);
                    depth = isSimple ? 2 : 3;
                    nodeType = XmlNodeType.Element;
                    internalState = EdiXmlReaderState.Value;
                    break;
                case EdiXmlReaderState.ComponentElement:
                    localName = nameTable.Add(COMPOSITE_ELEMENT_NAMES[currentElementIndex]);
                    depth = 2;
                    nodeType = XmlNodeType.Element;
                    internalState = EdiXmlReaderState.DataElement;
                    break;

                case EdiXmlReaderState.Value:
                    localName = string.Empty;
                    value = currentElement[currentComponentIndex];
                    if (value.Trim() == string.Empty)
                        nodeType = XmlNodeType.Whitespace;
                    else
                        nodeType = XmlNodeType.Text;
                    ++depth;
                    internalState = EdiXmlReaderState.DataElementEnd;
                    break;
                case EdiXmlReaderState.DataElementEnd:
                    bool isSimple2 = currentElement.IsSimpleDataElement();
                    localName = nameTable.Add(ELEMENT_NAMES[isSimple2 ? currentElementIndex : currentComponentIndex + 1]);
                    --depth;
                    nodeType = XmlNodeType.EndElement;
                    value = string.Empty;
                    if (isSimple2)
                        NextElement();
                    else
                        NextComponent();
                    break;
                case EdiXmlReaderState.ComponentElementEnd:
                    localName = nameTable.Add(COMPOSITE_ELEMENT_NAMES[currentElementIndex]);
                    depth = 2;
                    nodeType = XmlNodeType.EndElement;
                    NextElement();
                    break;
                case EdiXmlReaderState.SegmentEnd:
                    localName = nameTable.Add(currentSeg.SegId);
                    depth = 1;
                    nodeType = XmlNodeType.EndElement;
                    NextSegment();
                    break;
                case EdiXmlReaderState.RootElementEnd:
                    localName = rootElementName;
                    nodeType = XmlNodeType.EndElement;
                    internalState = EdiXmlReaderState.EndOfFile;
                    depth = 0;
                    break;
                case EdiXmlReaderState.EndOfFile:
                    xmlReaderState = ReadState.EndOfFile;
                    localName = string.Empty;
                    nodeType = XmlNodeType.None;
                    break;
                default:
                    throw new NotImplementedException(internalState.ToString());
            }
            return true;
        }
        void NextSegment()
        {
            currentSeg = baseReader.ReadSegment();
            currentElementIndex = 0;
            if (null == currentSeg)
                internalState = EdiXmlReaderState.RootElementEnd;
            else
                internalState = EdiXmlReaderState.SegmentElement;
        }
        void NextElement()
        {
            int ecnt = currentSeg.Count();
            while (currentElementIndex < ecnt)
            {
                ++currentElementIndex;
                currentElement = currentSeg[currentElementIndex];
                currentComponentIndex = 0;

                int ccnt = currentElement.Count();
                while (currentComponentIndex < ccnt)
                {
                    if (currentElement[currentComponentIndex] != string.Empty)
                    {
                        if (currentElement.IsSimpleDataElement())
                            internalState = EdiXmlReaderState.DataElement;
                        else
                            internalState = EdiXmlReaderState.ComponentElement;

                        return;
                    }
                    ++currentComponentIndex;
                }
            }
            internalState = EdiXmlReaderState.SegmentEnd;
        }
        void NextComponent()
        {
            int ccnt = currentElement.Count();
            ++currentComponentIndex;
            while (currentComponentIndex < ccnt)
            {
                if (currentElement[currentComponentIndex] != string.Empty)
                {
                    internalState = EdiXmlReaderState.DataElement;
                    return;
                }
                ++currentComponentIndex;
            }
            internalState = EdiXmlReaderState.ComponentElementEnd;
        }

        public override bool ReadAttributeValue()
        {
            return false;
        }

        public override ReadState ReadState
        {
            get { return xmlReaderState; }
        }

        public override void ResolveEntity()
        {
            throw new InvalidOperationException();
        }

        public override string Value
        {
            get { return value; }
        }
    }
}
